package cicd

import (
	"encoding/json"
	db "hulk-api/databases"
	"time"
	"math"
	"log"
	"github.com/garyburd/redigo/redis"
	"fmt"
	"strings"
	"os"
	"hulk-api/util/common"
	"hulk-api/util/command"
	"bytes"
	"io/ioutil"
	general "hulk-api/models/common"
	"hulk-api/models/k8s"
)

type Deployment struct {
	Name string 		`json:"name"`
	DeployMode string 	`json:"deploymode"`
	Describe string 	`json:"describe"`
	GitUrl string		`json:"giturl"`
	Branch string		`json:"branch"`
	CodeLang string		`json:"codelang"`
	Compile string		`json:"compile"`

	Hosts string		`json:"hosts"`

	ImageUrl string					`json:"imageUrl"`
	ImageTag string					`json:"imageTag"`
	DockerfileContent string		`json:"dockerfileContent"`
	DockerfilePath string			`json:"dockerfilePath"`

	YamlContent string 	`json:"yamlcontent"`
	YamlPath string		`json:"yamlpath"`



	LastStatus string	`json:"laststatus"`
	LastSuccess string	`json:"lastsuccess"`
	LastFail string		`json:"lastfail"`
	LastDurtime int64	`json:"lastdurtime"`
	StartTime string 	`json:"starttime"`

}

func (item *Deployment) Create() (  err error) {
	if strings.TrimSpace(item.DeployMode) == "ansible" {
		err = item.setAnsibleModel()
	} else if strings.TrimSpace(item.DeployMode) == "k8s" {
		err = item.setK8sModel()
	}
	return
}

func (item *Deployment) Save() (  err error) {

	if strings.TrimSpace(item.DeployMode) == "ansible" {
		err = item.saveAnsibleModelConfig()
	} else if strings.TrimSpace(item.DeployMode) == "k8s" {
		err = item.saveK8sModelConfig()
	}
	return

}



func (item *Deployment) GetDeployItems() ( items []Deployment , err error) {


	//sql := fmt.Sprintf(" select account,name,email,department,role,lastLoginTime from user")
	rows, err := db.SqlDB.Query("select name,deploy_mode,note,git_url,git_branch,code_lang,compile,hosts,image_url,image_tag,dockerfile_path,yaml_path,last_status,last_success,last_fail,last_exec_time from ansibleDeploymentItem")

	defer rows.Close()
	for rows.Next() {
		var i Deployment
		rows.Scan(&i.Name,&i.DeployMode,&i.Describe,&i.GitUrl,&i.Branch,&i.CodeLang,&i.Compile,&i.Hosts,&i.ImageUrl,&i.ImageTag,&i.DockerfilePath,&i.YamlPath,&i.LastStatus,&i.LastSuccess,&i.LastFail,&i.LastDurtime)
		items = append(items, i)
	}
	if err = rows.Err(); err != nil {
		log.Println("get deployment items error: ",err.Error())
		return
	}

	return

}
func (item *Deployment) GetDeployItem() (  err error) {

	//sql := fmt.Sprintf(" select account,name,email,department,role,lastLoginTime from user")
	row := db.SqlDB.QueryRow("select name,deploy_mode,note,git_url,git_branch,code_lang,compile,hosts,image_url,image_tag,dockerfile_path,yaml_path,last_status,last_success,last_fail,last_exec_time from ansibleDeploymentItem where name=?",item.Name)

	if err =row.Scan(&item.Name,&item.DeployMode,&item.Describe,&item.GitUrl,&item.Branch,&item.CodeLang,&item.Compile,&item.Hosts,&item.ImageUrl,&item.ImageTag,&item.DockerfilePath,&item.YamlPath,&item.LastStatus,&item.LastSuccess,&item.LastFail,&item.LastDurtime); err != nil{
		log.Println(err)
		return
	}
	return

}



func (item *Deployment) setAnsibleModel() (  err error) {
	item.YamlPath = "/workspace/" + item.Name
	stmt, err := db.SqlDB.Prepare("insert into ansibleDeploymentItem(name,deploy_mode,note,git_url,git_branch,code_lang,compile,hosts,yaml_path) values (?,?,?,?,?,?,?,?,?)")
	if err != nil {
		log.Println(err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec(item.Name,item.DeployMode, item.Describe,item.GitUrl,item.Branch,item.CodeLang,item.Compile,item.Hosts,item.YamlPath)
	if err != nil {
		log.Println(err)
		return
	}

	jobPath := "workspace/" + item.Name
	err = common.CreateIfNotExist(jobPath)
	if err != nil {
		log.Println(err)
		return
	}
	yamlpath := "workspace/" +  item.Name + "/" + item.Name + ".yaml"
	err = ioutil.WriteFile(yamlpath,[]byte(item.YamlContent),0666)
	if err != nil {
		log.Println(err)
		return
	}
	h := &general.Host{
		Name: item.Hosts,
	}
	err = h.GetHost()
	hostsPath := "workspace/" +  item.Name + "/" + "hosts"
	err = ioutil.WriteFile(hostsPath,[]byte(strings.Replace(h.IpList,",","\n",-1)),0666)
	if err != nil {
		log.Println(err)
		return
	}
	return
}
func (item *Deployment) setK8sModel() (  err error) {
	item.YamlPath = "/workspace/" + item.Name
	item.DockerfilePath = "/workspace/" + item.Name
	stmt, err := db.SqlDB.Prepare("insert into ansibleDeploymentItem(name,deploy_mode,note,git_url,git_branch,code_lang,compile,image_url,image_tag,dockerfile_path,yaml_path) values (?,?,?,?,?,?,?,?,?,?,?)")
	if err != nil {
		log.Println("here1")
		log.Println(err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec(item.Name,item.DeployMode, item.Describe,item.GitUrl,item.Branch,item.CodeLang,item.Compile,item.ImageUrl,item.ImageTag,item.DockerfilePath,item.YamlPath)
	if err != nil {
		log.Println("here2")
		log.Println(err)
		return
	}

	jobPath := "workspace/" + item.Name
	err = common.CreateIfNotExist(jobPath)
	if err != nil {
		log.Println(err)
		return
	}

	dockerfile := "workspace/" +  item.Name + "/" + "Dockerfile"
	err = ioutil.WriteFile(dockerfile,[]byte(item.DockerfileContent),0666)
	if err != nil {
		log.Println(err)
		return
	}

	yamlpath := "workspace/" +  item.Name + "/" + item.Name + ".yaml"
	err = ioutil.WriteFile(yamlpath,[]byte(item.YamlContent),0666)
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (item *Deployment) saveAnsibleModelConfig() (  err error) {
	item.YamlPath = "/workspace/" + item.Name
	//stmt, err := db.SqlDB.Prepare("insert into ansibleDeploymentItem(name,deploy_mode,note,git_url,git_branch,code_lang,compile,hosts,yaml_path) values (?,?,?,?,?,?,?,?,?)")
	stmt, err := db.SqlDB.Prepare("update ansibleDeploymentItem set deploy_mode=?,note=?,git_url=?,git_branch=?,code_lang=?,compile=?,hosts=?,yaml_path=? where name = ?")
	if err != nil {
		log.Println(err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec(item.DeployMode, item.Describe,item.GitUrl,item.Branch,item.CodeLang,item.Compile,item.Hosts,item.YamlPath,item.Name)
	if err != nil {
		log.Println(err)
		return
	}

	jobPath := "workspace/" + item.Name
	err = common.CreateIfNotExist(jobPath)
	if err != nil {
		log.Println(err)
		return
	}
	yamlpath := "workspace/" +  item.Name + "/" + item.Name + ".yaml"
	err = ioutil.WriteFile(yamlpath,[]byte(item.YamlContent),0666)
	if err != nil {
		log.Println(err)
		return
	}
	h := &general.Host{
		Name: item.Hosts,
	}
	err = h.GetHost()
	hostsPath := "workspace/" +  item.Name + "/" + "hosts"
	err = ioutil.WriteFile(hostsPath,[]byte(strings.Replace(h.IpList,",","\n",-1)),0666)
	if err != nil {
		log.Println(err)
		return
	}
	return
}
func (item *Deployment) saveK8sModelConfig() (  err error) {
	item.YamlPath = "/workspace/" + item.Name
	item.DockerfilePath = "/workspace/" + item.Name
	stmt, err := db.SqlDB.Prepare("update ansibleDeploymentItem set deploy_mode=?,note=?,git_url=?,git_branch=?,code_lang=?,compile=?,image_url=?,image_tag=?,dockerfile_path=?,yaml_path=? where name=?")
	if err != nil {
		log.Println("here1")
		log.Println(err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec(item.DeployMode, item.Describe,item.GitUrl,item.Branch,item.CodeLang,item.Compile,item.ImageUrl,item.ImageTag,item.DockerfilePath,item.YamlPath,item.Name)
	if err != nil {
		log.Println("here2")
		log.Println(err)
		return
	}

	jobPath := "workspace/" + item.Name
	err = common.CreateIfNotExist(jobPath)
	if err != nil {
		log.Println(err)
		return
	}

	dockerfile := "workspace/" +  item.Name + "/" + "Dockerfile"
	err = ioutil.WriteFile(dockerfile,[]byte(item.DockerfileContent),0666)
	if err != nil {
		log.Println(err)
		return
	}
	if strings.TrimSpace(item.YamlContent) == "" {
		return
	}

	yamlpath := "workspace/" +  item.Name + "/" + item.Name + ".yaml"
	err = ioutil.WriteFile(yamlpath,[]byte(item.YamlContent),0666)
	if err != nil {
		log.Println(err)
		return
	}

	return
}



func(item Deployment) IsRuning() (  bool , error)  {
	key := "deploying-" + item.Name
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()

	exist, err := redis.Int(redisconn.Do("EXISTS",key))
	if exist == 1 {
		return true,err
	} else {
		return false,err
	}

}


func (item Deployment) Progress() ( result string ,visble bool, err error) {

	items := make([]Deployment,0)
	rows, err := db.SqlDB.Query("select name,start_time,last_exec_time from ansibleDeploymentItem where status=1;")
	defer rows.Close()
	for rows.Next() {
		var i Deployment
		rows.Scan(&i.Name,&i.StartTime,&i.LastDurtime)
		items = append(items, i)
	}
	if err = rows.Err(); err != nil {
		return
	}
	if len(items) == 0 {
		visble = false
		return
	}
	progessMap := make(map[string]map[string]interface{})
	for _,i := range items {
		progessMap[i.Name] = map[string]interface{}{
			"status":true,
			"precent": i.progress(),
		}
	}

	dataByte,err := json.Marshal(progessMap)
	if err != nil {
		log.Println(err)
	}
	result = string(dataByte)
	visble =true
	return
}


//TODO: 优化算法
func (item *Deployment) progress() (precent int) {
	timeTmp := "2006-01-02 15:04:05"
	t,err := time.ParseInLocation(timeTmp, item.StartTime, time.Local)
	if err != nil {
		log.Println(err)
	}
	timeExeced := int64(math.Floor(time.Now().Sub(t).Seconds()))

	if timeExeced > item.LastDurtime {
		precent = 99   //TODO: color red
	} else {
		precent = int(100*timeExeced / item.LastDurtime)
	}
	return

}


func (item *Deployment) Start() ( buildId string ,err error) {

	//分布式锁
	item.RunLock()
	defer item.RunUnLock()




	starTime := time.Now()
	item.setRuningStatus()
	defer item.setDoneStatus()  //让前端轮训此次构建的完成


	//test
	time.Sleep(20 * time.Second)

	fmt.Println("debug start deploy:" , item.Name)
	workPath := strings.TrimSpace(item.YamlPath)[1:]
	fmt.Println("工作空间 :" , workPath)
	common.CreateIfNotExist(workPath)   //误删除目录
	//exec.Command("git" ,"init" , )
	//入库统计 go item.insert()
	bdir := workPath + "/builds"
	//var thisBuildJobDir string
	thisBuildJobDir,err := item.createCurrentBuildDir(bdir)
	if err != nil {
		go item.done(starTime,"失败","")
		return
	}
	buildId = thisBuildJobDir


	outPutFilePath := bdir + "/" + thisBuildJobDir + "/" + "output.txt"
	statusPath := bdir + "/" + thisBuildJobDir + "/" + "status"
	f,err := os.OpenFile(outPutFilePath,os.O_RDWR | os.O_CREATE|os.O_APPEND,0666)
	if err != nil {
		log.Println(err)
		go item.done(starTime,"失败",statusPath)
		return
	} else {
		f.WriteString("Building in workspace " + workPath + "\n")
		ff,e := os.OpenFile(outPutFilePath,os.O_RDWR | os.O_CREATE|os.O_APPEND,0666)
		if e == nil {
			ff.WriteString("构建中")
			ff.Close()
		}


	}
	defer f.Close()


	//codeDir := workPath + "/code"    //创建工作目录 code 没有/会有问题
	codeDir := workPath + "/code"    //创建工作目录
	common.CreateIfNotExist(codeDir)

	//判断.git本地仓库是否存在
	//如果第一次需要git init

	//cmd := &command.Command{}
	cmd,err := command.NewCmd(&command.Command{})
	if err != nil {
		go item.done(starTime,"失败",statusPath)
		return
	}

	err = item.gitInit(f,cmd,codeDir)
	if err != nil {
		go item.done(starTime,"失败",statusPath)
		log.Println(err.Error())
		return
	}

	err = item.gitConfigRemoteUrl(f,cmd,codeDir)
	if err != nil {
		go item.done(starTime,"失败",statusPath)
		log.Println(err.Error())
		return
	}


	//获取分支
	err = item.gitFetchBranchs(f,cmd,codeDir)
	if err != nil {
		go item.done(starTime,"失败",statusPath)
		log.Println(err.Error())
		return
	}

	//获取指定分支最后一次commit id
	commitid,err := item.gitLastCommitId(f,cmd,codeDir)
	if err != nil {
		go item.done(starTime,"失败",statusPath)
		log.Println(err.Error())
		return
	}


	//拉取代码
	err = item.gitPull(f,cmd,codeDir,commitid)
	if err != nil {
		fmt.Println(err.Error())
		go item.done(starTime,"失败",statusPath)
		log.Println(err.Error())
		return
	}


	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	//for test
	f.WriteString(fmt.Sprintf(" > 编译  sleep 5 \n" ))
	fmt.Println("编译  sleep 5 ",item.Compile)  //golang 先省略
	time.Sleep(10 * time.Second)
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//deploy start

	if item.DeployMode == "ansible" {
		err = item.deployWithAnsible(f,cmd,workPath)
	}else {
		err = item.deployWithK8s(f,cmd,workPath)
	}

	if err != nil {
		log.Println(err)
		go item.done(starTime,"失败",statusPath)
		log.Println(err.Error())
		return
	}
	go item.done(starTime,"成功",statusPath)
	return

}



func (item *Deployment) RunLock() {
	r_key := "deploying-" + item.Name
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()
	_, err := redisconn.Do("SET", r_key, "starting")
	if err != nil {
		log.Println(err)
	}

}

func (item *Deployment) RunUnLock() {
	r_key := "deploying-" + item.Name
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()
	_, err := redisconn.Do("DEL", r_key)
	if err != nil {
		log.Println(err)
	}

}

func (item *Deployment) setRuningStatus()  {
	//入库,状态设为构建状态
	//并且将以此为持续构建时间推论
	stmt, err := db.SqlDB.Prepare("update ansibleDeploymentItem set status=1 , start_time=? where name=?")
	if err != nil {
		log.Println(err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec(time.Now().Format("2006-01-02 15:04:05"),item.Name)
	if err != nil {
		log.Println(err)
		return
	}

}

func (item *Deployment) setDoneStatus() {

	stmt, err := db.SqlDB.Prepare("update ansibleDeploymentItem set status=0 where name=?")
	if err != nil {
		log.Println(err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec(item.Name)
	if err != nil {
		log.Println(err)
		return
	}

}

func (item *Deployment) done(starttime time.Time,status string,statusPath string) (  ) {
	if strings.TrimSpace(statusPath) != "" {
		//f,err := os.OpenFile(statusPath,os.O_RDWR | os.O_CREATE|os.O_APPEND,0666)
		f,err := os.OpenFile(statusPath,os.O_RDWR | os.O_CREATE,0666)
		if err != nil {
			log.Println(err)
		} else {
			f.WriteString(status)
		}
		defer f.Close()
	}

	//入库,状态设为未构建
	//TODO: 更新持续构建时间
	execTotalTime := math.Floor(time.Now().Sub(starttime).Seconds())
	stmt, err := db.SqlDB.Prepare("update ansibleDeploymentItem set last_fail=? , last_status=? , last_exec_time=? where name=?")
	if err != nil {
		log.Println(err)
		return
	}
	defer stmt.Close()

	_, err = stmt.Exec(time.Now().Format("2006-01-02 15:04:05"),status,execTotalTime,item.Name)
	if err != nil {
		log.Println(err)
		return
	}

}


func (item *Deployment) createCurrentBuildDir (bdir string)(nextOutputDir string , err error){

	err = common.CreateIfNotExist(bdir)
	if err != nil {
		log.Println(err)
		return
	} else {
		nextOutputDir ,err = common.BuildPath(bdir).CreateNextBuildDir()
		if err != nil {
			log.Println(err)
			return
		}
	}
	return
}


func (item *Deployment) gitInit(f *os.File,cmd *command.Command,codeDir string)(err error) {
	f.WriteString(" > git rev-parse --is-inside-work-tree \n")
	cmdLine := "rev-parse --is-inside-work-tree"
	cmd.SetGitCmd(codeDir,cmdLine)
	//cmd.SetStd(f)
	//cmd.RunOnce()
	s := bytes.Buffer{}
	cmd.SetStd(&s)
	err = cmd.RunOnce()
	if err != nil {
		f.WriteString(err.Error() + "\n")
	}
	f.WriteString(string(s.Bytes()) + "\n")
	if strings.TrimSpace(string(s.Bytes())) != "true" {
		f.WriteString(" > git init \n")
		cmdLine = "init"
		cmd.SetGitCmd(codeDir,cmdLine)
		cmd.SetStd(f)
		err = cmd.RunOnce()
	}
	return
}
func (item *Deployment) gitConfigRemoteUrl(f *os.File,cmd *command.Command,codeDir string)(err error) {
	f.WriteString(fmt.Sprintf(" > git config remote.origin.url %s \n " , item.GitUrl))
	cmdLine := fmt.Sprintf("config remote.origin.url %s" , item.GitUrl)
	cmd.SetGitCmd(codeDir,cmdLine)
	cmd.SetStd(f)
	err = cmd.RunOnce()

	//f.WriteString(fmt.Sprintf("%s \n %s", cmd.Stdout() ,cmd.Stderr()) )
	return
}
func (item *Deployment) gitFetchBranchs(f *os.File,cmd *command.Command,codeDir string)(err error) {
	f.WriteString(fmt.Sprintf(" > git fetch --tags --progress %s +refs/heads/*:refs/remotes/origin/* \n" , item.GitUrl))
	cmdLine := fmt.Sprintf("fetch --tags --progress %s +refs/heads/*:refs/remotes/origin/*" , item.GitUrl)
	cmd.SetGitCmd(codeDir,cmdLine)
	cmd.SetStd(f)
	err = cmd.RunOnce()
	//f.WriteString(fmt.Sprintf("%s \n %s", cmd.Stdout() ,cmd.Stderr()) )
	return
}
func (item *Deployment) gitLastCommitId(f *os.File,cmd *command.Command,codeDir string)(commitid string,err error) {
	f.WriteString(fmt.Sprintf(" > git rev-parse refs/remotes/origin/%s^{commit} \n" , item.Branch))
	cmdLine := fmt.Sprintf("rev-parse refs/remotes/origin/%s^{commit}" , item.Branch)
	cmd.SetGitCmd(codeDir,cmdLine)
	s := bytes.Buffer{}
	cmd.SetStd(&s)
	err = cmd.RunOnce()
	if err != nil {
		f.WriteString(string(s.Bytes()))
		f.WriteString(err.Error())
		return
	}
	commitid = string(s.Bytes())

	//f.WriteString(fmt.Sprintf("%s \n %s", cmd.Stdout() ,cmd.Stderr()) )
	return
}

func (item *Deployment) gitPull(f *os.File,cmd *command.Command,codeDir string,commitid string)(err error) {
	f.WriteString(fmt.Sprintf(" > git checkout -f %s \n" , commitid))
	commitid=""
	cmdLine := fmt.Sprintf("checkout -f %s" , commitid)
	//cmd.SetGitCmd(codeDir + "/" ,cmdLine)  ??how
	cmd.SetGitCmd(codeDir ,cmdLine)
	cmd.SetStd(f)
	err = cmd.RunOnce()

	if err != nil {
		log.Println(err.Error())
	}
	//f.WriteString(fmt.Sprintf("%s \n %s", cmd.Stdout() ,cmd.Stderr()) )
	return
}

func (item *Deployment) deployWithAnsible(f *os.File,cmd *command.Command,cmddir string)(err error) {
	f.WriteString(fmt.Sprintf(" > 开始执行任务 \n" ))
	f.WriteString(fmt.Sprintf(" + ansible-playbook -i hosts %s.yaml \n" , item.Name ))
	cmdLine := fmt.Sprintf("ansible-playbook -i hosts %s.yaml" , item.Name )
	cmd.SetCmd(cmddir,cmdLine)
	err = cmd.RunOnce()
	f.WriteString(fmt.Sprintf("%s \n %s", cmd.Stdout() ,cmd.Stderr()) )
	if err != nil {
		f.WriteString(err.Error() + "\n")
	}
	return
}

func (item *Deployment) deployWithK8s(f *os.File,cmd *command.Command,cmddir string)(err error) {
	f.WriteString(fmt.Sprintf(" > 开始执行任务 budepoy into k8s ... \n" ))
	yamlPath := fmt.Sprintf("%s/%s.yaml",item.YamlPath[1:],item.Name)
	err = k8s.DeployAppFromFile(yamlPath)
	if err != nil {
		f.WriteString(err.Error())
	}
	return
}



//output
func (item *Deployment) LoadOutput() ( outLists []Output , err error) {
	//入库
	workspace := "workspace/" + item.Name + "/builds"
	outLists,err = ParseOutputDir(workspace)
	return

}


func (item *Deployment) loadDeployItemByname() (  err error) {

	row := db.SqlDB.QueryRow("select deploy_mode,note,git_url,git_branch,code_lang,compile,hosts,yaml_path,last_status,last_success,last_fail,last_exec_time from ansibleDeploymentItem where name=?" , item.Name)
	if err =row.Scan(&item.DeployMode,&item.Describe,&item.GitUrl,&item.Branch,&item.CodeLang,&item.Compile,&item.Hosts,&item.YamlPath,&item.LastStatus,&item.LastSuccess,&item.LastFail,&item.LastDurtime); err != nil{
		fmt.Printf("scan failed, err:%v",err)
		return
	}
	return


}